package com.hmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.hmall.common.PageDto;
import com.hmall.common.client.ItemClient;
import com.hmall.item.pojo.Item;
import com.hmall.search.dto.SearchDto;
import com.hmall.search.pojo.ItemDoc;
import com.hmall.search.service.ISearchService;
import org.apache.http.HttpHost;
import org.aspectj.weaver.ast.Var;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService implements ISearchService {

    @Autowired
    private ItemClient itemClient;

    private RestHighLevelClient restHighLevelClient =
            new RestHighLevelClient(RestClient.builder(HttpHost.create("http://139.9.37.235:9200")));

    //批量导入
    @Override
    public void bulk() throws Exception {
        //1 使用itemClient远程调用item-service服务的分页查询方法，获取所有商品信息
        int page=1,size=100;
        PageDto<Item> pageDto = itemClient.queryItemByPage(page, size);
        //2 批量导入到es中
        while (pageDto.getList().size()>0){
            //2.1 创建请求对象
            BulkRequest bulkRequest = new BulkRequest("item");
            //2.2 构建DSL语句
                //2.2.1 获取所有商品并遍历
                pageDto.getList().forEach(item -> {
                    //2.2.1 如果是下架的商品就不添加
                    if(item.getStatus()==2){
                        return;
                    }
                    //2.2.2 不进if说明要导入，将Item转换成ItemDoc对象
                    ItemDoc itemDoc=new ItemDoc(item);
                    //2.2.3 添加到bulkRequest中
                    bulkRequest.add(new IndexRequest()
                            .id(itemDoc.getId().toString())
                            .source(JSON.toJSONString(itemDoc), XContentType.JSON));
                });
               //如果有数据就继续查询下一页数据，直到没有查到数据就退出行
               pageDto = itemClient.queryItemByPage(++page, size);
            //2.3 发送请求
            restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
        }
    }

    // 分页搜索
    @Override
    public PageDto<ItemDoc> search(SearchDto searchDto) throws Exception {
        //1 创建请求对象
        SearchRequest searchRequest=new SearchRequest("item");
        //2 构建DSL查询条件
        buildBaseQuery(searchDto,searchRequest);
        //3 发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //4 处理结果
        PageDto<ItemDoc> pageDto=handleResponse(response);
        return pageDto;
    }

    //获取筛选条件
    /*
    GET item/_search
        {
          "query": {

            "function_score": {
              "query": {
                  "bool": {
                    "must": [],
                    "filter": []
                }
              },
              "functions": [],
              "boost_mode": "multiply"
            }
          },
          "sort": [],
          "from": 0,
          "size": 20,
          "highlight": {},
          "aggs": {
            "brandAgg": {  //brandAgg任意名称，后期根据这个名称获取
              "terms": {
                "field": "brand",
                "size": 100
              }
            },
            "categoryAgg": {  //brandAgg任意名称，后期根据这个名称获取
              "terms": {
                "field": "category",
                "size": 100
              }
            }
          }
        }
     */
    @Override
    public Map<String, List<String>> getFilters(SearchDto searchDto) throws Exception {
        //1 创建请求对象
        SearchRequest searchRequest=new SearchRequest("item");
        //2 构建DSL查询条件
        buildBaseQuery(searchDto,searchRequest);
        //设置聚合查询条件
        searchRequest.source()
                .aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));
        searchRequest.source()
                .aggregation(AggregationBuilders.terms("categoryAgg").field("category").size(100));

        //3 发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //4 获取聚合结果
        //4.1 获取所有聚合数据
        Aggregations aggregations = response.getAggregations();
        //4.2 获取品牌聚合数据
        Terms brandTerms = aggregations.get("brandAgg");
        List<String> brands = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        //4.2 获取分类聚合数据
        Terms categoryTerms = aggregations.get("categoryAgg");
        List<String> categorys = categoryTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        //4.3 封装到map集合中返回
        Map<String,List<String>> map=new HashMap<>();
        map.put("brand",brands);
        map.put("category",categorys);
        return map;
    }

    //获取关键词推荐
    /*
        GET item/_search
        {
          "suggest": {
            "item_suggest": {
              "text": "sj",
              "completion": {
                "field":"suggestion",
                "skip_duplicates":true,
                "size":10
              }
            }
          }
        }
     */
    @Override
    public List<String> getSuggestion(String prefix) throws Exception {
        //1 创建请求对象
        SearchRequest searchRequest=new SearchRequest("item");
        //2 构建DSL查询条件:设置自动补全字段
        searchRequest.source().suggest(new SuggestBuilder().addSuggestion(
                        "item_suggest",
                               SuggestBuilders.completionSuggestion("suggestion")
                                              .prefix(prefix)
                                              .skipDuplicates(true)
                                              .size(10)));
        //3 发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //4 处理结果
        //4.1 获取所有自动补全结果
        Suggest suggest = response.getSuggest();
        //4.2 获取名称为hotel_suggest自动补全结果
        CompletionSuggestion suggestion= suggest.getSuggestion("item_suggest");
        //4.3 获取结果中的文本并转存到list集合中
        List<String> list = suggestion.getOptions().stream().map(option -> option.getText().toString()).collect(Collectors.toList());
        return list;
    }

    /**
     * 新增或者修改文档
     * @param itemDoc
     */
    @Override
    public void addOrUpdateItemDoc(ItemDoc itemDoc) throws IOException {
        //1 创建请求对象
        IndexRequest indexRequest=new IndexRequest("item");
        //2 构建DSL:新增或者修改文档
        indexRequest.id(itemDoc.getId().toString())
                .source(JSON.toJSONString(itemDoc),XContentType.JSON);
        //3 发送请求
        restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
    }

    /**
     * 删除文档
     * @param id
     */
    @Override
    public void deleteItemDoc(Long id) throws IOException {
        //1 创建请求对象
        DeleteRequest deleteRequest=new DeleteRequest("item");
        //2 构建DSL:删除文档
        deleteRequest.id(id+"");
        //3 发送请求
        restHighLevelClient.delete(deleteRequest,RequestOptions.DEFAULT);
    }

    //构建搜索条件
    /*
        GET item/_search
        {
          "query": {
            "function_score": {  //算分函数
              "query": {   //构建普通查询条件
                  "bool": {  //布尔查询
                    "must": [  //分词查询，参数算分
                      {
                        "match": {
                          "all": "格力电风扇"
                        }
                      }
                    ],
                    "filter": [  //过滤，不参与算分
                      {
                        "term": {   //品牌过滤
                          "brand": "格力"
                        }
                      },
                      {
                        "term": {  //分类过滤
                          "category": "电风扇"
                        }
                      },
                      {
                        "range": {  //价格过滤
                          "price": {
                            "gte": 1000,
                            "lte": 15000
                          }
                        }
                      }
                    ]
                }
              },
              "functions": [   //广告竞价排名
                {
                  "filter": {
                    "term": {
                      "isAD": true
                    }
                  },
                  "weight": 10
                }
              ],
              "boost_mode": "multiply"
            }
          },
          "sort": [  //根据销量sold或者price排名，二选一
            {
              "sold": {
                "order": "desc"
              },
              "price": {
                "order": "desc"
              }
            }
          ],
          "from": 0,    //分页起始索引
          "size": 20,   //分页每页条数
          "highlight": {  //高亮查询
            "fields": {
              "name": {
                "require_field_match":false
              }
            }
          }
        }
     */
    private void buildBaseQuery(SearchDto searchDto, SearchRequest searchRequest) {
        //1 构建布尔查询条件
        BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery();
        //1.1 根据关键字key查询
        if(StringUtils.hasText(searchDto.getKey())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("all",searchDto.getKey()));
        }
        //1.2 分类过滤
        if(StringUtils.hasText(searchDto.getBrand())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand",searchDto.getBrand()));
        }
        //1.3 品牌过滤
        if(StringUtils.hasText(searchDto.getCategory())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category",searchDto.getCategory()));
        }
        //1.4 价格过滤
        if(searchDto.getMinPrice()!=null && searchDto.getMaxPrice()!=null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(searchDto.getMinPrice()).lte(searchDto.getMaxPrice()));
        }
        //2 设置查询：普通查询条件+算分函数
        searchRequest.source().query(QueryBuilders.functionScoreQuery(
                boolQueryBuilder, //原始的bool查询，相关性算分的查询
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{// function score的数组
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            QueryBuilders.termQuery("isAD",true),  //竞价条件，isAD是广告就加大权重
                            ScoreFunctionBuilders.weightFactorFunction(10) //设置权重
                    )
                }
            ).boostMode(CombineFunction.MULTIPLY)  //原始的分数乘以设置的权重
        );
        //3 设置分页
        searchRequest.source().from((searchDto.getPage()-1)*searchDto.getSize()).size(searchDto.getSize());
        //4 设置高亮
        searchRequest.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        //5 设置排序
        if("sold".equals(searchDto.getSortBy())){
            searchRequest.source().sort("sold",SortOrder.DESC);
        }else if("price".equals(searchDto.getSortBy())){
            searchRequest.source().sort("price",SortOrder.DESC);
        }
    }

    //处理搜索结果
    private PageDto<ItemDoc> handleResponse(SearchResponse response) {
        //1 获取总条数
        long total = response.getHits().getTotalHits().value;
        //2 获取命中的数据
        SearchHit[] hits = response.getHits().getHits();
        //3 遍历结果，封装成List<ItemDoc>
        List<ItemDoc> list = Arrays.stream(hits).map(hit -> {
            //3.1 获取json数据
            String json = hit.getSourceAsString();
            //3.2 转换成java对象
            ItemDoc itemDoc = JSON.parseObject(json, ItemDoc.class);
            //3.3 设置高亮字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {  //有高亮字段
                //获取name高亮字段
                String name = highlightFields.get("name").fragments()[0].string();
                //替换成高亮字段
                itemDoc.setName(name);
            }
            return itemDoc;
        }).collect(Collectors.toList());
        //4 创建分页对象PageDto封装分页结果并返回
        PageDto<ItemDoc> pageDto=new PageDto<>(total,list);
        return pageDto;
    }
}
